package com.bolt.admin.logging.aop;

import com.alibaba.fastjson.JSON;
import com.bolt.admin.logging.annotation.LogAnnotation;
import com.bolt.admin.logging.util.TraceConstant;
import com.bolt.admin.logging.util.TraceUtil;
import com.bolt.admin.module.log.entity.LogEntity;
import com.bolt.admin.module.log.service.LogService;
import com.bolt.admin.security.shiro.AuthContextHolder;
import com.bolt.admin.security.shiro.AuthUserDetails;
import com.bolt.common.utils.JacksonUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.common.web.RequestUtil;
import io.netty.util.internal.ThrowableUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.core.task.TaskExecutor;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * Created by Administrator on 2020/2/13.
 */
@Slf4j
@Aspect
@Order(-1) // 保证该AOP在@Transactional之前执行
public class LogAnnotationAOP {

    @Autowired(required = false)
    private LogService logService;


    @Autowired
    private TaskExecutor taskExecutor;


    @Around("@annotation(ds)")
    public Object logSave(ProceedingJoinPoint joinPoint, LogAnnotation ds) throws Throwable {

        // 请求流水号
        String transid = StrUtil.nullToDefault(TraceUtil.getTrace(), MDC.get(TraceConstant.LOG_TRACE_ID));
        // 记录开始时间
        long start = System.currentTimeMillis();
        // 获取方法参数
        Object result = null;
        LogEntity sysLog = new LogEntity();
        sysLog.setCreateTime(new Date());
        AuthUserDetails loginAppUser = AuthContextHolder.getAuthUserDetails();
        if (loginAppUser != null) {
            sysLog.setUserId(loginAppUser.getUserId());
            sysLog.setUserName(loginAppUser.getUserName());
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        LogAnnotation logAnnotation = signature.getMethod().getDeclaredAnnotation(LogAnnotation.class);
        sysLog.setModule(logAnnotation.module());
        sysLog.setDescription(logAnnotation.description());
        // 方法路径
        String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()";
        sysLog.setMethod(methodName);
        Method method = signature.getMethod();
        String params = getParameter(method, joinPoint.getArgs());
        sysLog.setParams(params);
        try {
            HttpServletRequest request = getHttpServletRequest();
            sysLog.setBrowser(RequestUtil.getBrowser(request));
            sysLog.setRequestIp(RequestUtil.getRemoteIpAddress(request));
            // 调用原来的方法
            result = joinPoint.proceed();
            sysLog.setSuccess(Boolean.TRUE);
            sysLog.setTime(System.currentTimeMillis() - start);
        } catch (Exception e) {
            sysLog.setSuccess(Boolean.FALSE);
            sysLog.setTime(System.currentTimeMillis() - start);
            sysLog.setExceptionDetail(ThrowableUtil.stackTraceToString(e));
            log.error("请求报错，transid={},  url={} , httpMethod={}, reqData={} ,error ={} ", transid, getHttpServletRequest().getRequestURI(), methodName, params, e.getMessage());
            throw e;
        } finally {

            //如果需要记录数据库开启异步操作
            if (logAnnotation.recordRequestParam()) {
                CompletableFuture.runAsync(() -> {
                    try {
                        log.trace("日志落库开始：{}", sysLog);
                        if (logService != null) {
                            logService.save(sysLog);
                        }
                        log.trace("开始落库结束：{}", sysLog);


                    } catch (Exception e) {
                        log.error("落库失败：{}", e.getMessage());
                    }

                }, taskExecutor);
            }

            // 获取回执报文及耗时
            log.debug("请求完成, transid={}, 耗时={}, resp={}:", transid, (System.currentTimeMillis() - start),
                    result == null ? null : JSON.toJSONString(result));

        }
        return result;
    }

    /**
     * 根据方法和传入的参数获取请求参数
     */
    private String getParameter(Method method, Object[] args) {
        List <Object> argList = new ArrayList <>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            //将RequestParam注解修饰的参数作为请求参数
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                Map <String, Object> map = new HashMap <>();
                String key = parameters[i].getName();
                if (!StrUtil.isEmpty(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        if (argList.size() == 0) {
            return "";
        }
        return argList.size() == 1 ? JacksonUtil.toJSONString(argList.get(0)) : JacksonUtil.toJSONString(argList);
    }

    private HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
    }

}

 